#include "AudioOut.h"
#include "Source.h"
#include "Header.h"
#include <windows.h>

AudioOut::AudioOut() :
	_deviceList(0),
	_deviceTable(0),
	_run(false),
	_ctx(0),
	_currentDevice(0)
{

}

AudioOut::~AudioOut()
{
	delete [] this->_deviceList;
	delete [] this->_deviceTable;

	if (this->_currentDevice != NULL)
		alcCloseDevice(this->_currentDevice);
	std::map<IBuffer*, ISource*>::iterator it = this->_sources.begin();
	std::map<IBuffer*, ISource*>::iterator end = this->_sources.end();

	for(; it != end; ++it)
	{
		delete (*it).second;
	}
}

void	AudioOut::UpdateSourceConfiguration(IBuffer *buffer, Header *header)
{
	if (!this->_sources.count(buffer))
	{
		this->AddSource(buffer, header);
	}
	else
	{
		ALenum format;

		if (header->audio->format == STEREO16)
			format = AL_FORMAT_STEREO16;
		else if  (header->audio->format == STEREO8)
			format = AL_FORMAT_STEREO8;
		else if  (header->audio->format == MONO16)
			format = AL_FORMAT_MONO16;
		else
			format = AL_FORMAT_MONO8;
		if (this->_sources[buffer]->GetFormat() != format 
			|| this->_sources[buffer]->GetFrequence() != header->audio->frequence)
		{
			/*
			bool stop = this->_sources[buffer]->IsRunning();
			if (stop)
				this->_sources[buffer]->Stop();
//			this->_sources.erase(buffer);
			this->AddSource(buffer, header);
			if (!stop)
				this->_sources[buffer]->Stop();*/
			this->_sources[buffer]->SetFormat(format);
			this->_sources[buffer]->SetFrequence(header->audio->frequence);
		}
	}
}

void	AudioOut::AddSource(IBuffer *buffer, Header *header)
{
	ALenum format;

	if (header->audio->format == STEREO16)
		format = AL_FORMAT_STEREO16;
	else if  (header->audio->format == STEREO8)
		format = AL_FORMAT_STEREO8;
	else if  (header->audio->format == MONO16)
		format = AL_FORMAT_MONO16;
	else
		format = AL_FORMAT_MONO8;

	this->_sources[buffer] = new Source(format , header->audio->frequence, this->_run);
//checkError("addSource");
	buffer->SubscribeEnoughData(&AudioOut::treat, 0, this->_sources[buffer]); // TODO 0 a mettre en param
}


void	AudioOut::RemoveSource(IBuffer *buffer)
{
	ISource *source = this->_sources[buffer];

	buffer->UnsubscribeEnoughData(&AudioOut::treat, 0); // TODO 0 a mettre en memoire / source
	delete source;
	this->_sources.erase(buffer);
}
	//void	RemoveSource(unsigned int id);

void			AudioOut::StopSource(IBuffer *buffer)
{
	this->_sources[buffer]->Stop();
}
//	void			StopSource(unsigned int id);

void			AudioOut::StartSource(IBuffer *buffer)
{
	this->_sources[buffer]->Start();
}
	//void			StartSource(unsigned int id);

unsigned int	AudioOut::GetNumberOfSources()
{
	return this->_sources.size();
}

char **AudioOut::DetectDevice()
{
	// test if lister is avaible 
	if(alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"))
	{
		int numDevice = 0;
		char *ptr;

		// delete old device list if exist
		if (this->_deviceList != NULL)
		{
			delete [] this->_deviceList;
		}
		// get list of device
		this->_deviceList = (char*)alcGetString
			(
			NULL, 
			ALC_ALL_DEVICES_SPECIFIER
			);
		if (this->_deviceList == "")
		{
			return (char **)-1;
		}
		// count devices
		ptr = this->_deviceList;
		while( ptr && *ptr )
		{
			while (*ptr != NULL)
				++ptr;
			ptr++;
			numDevice++;
		}
		// delete old device table if exist
		if (this->_deviceTable != NULL)
		{
			delete []this->_deviceTable;
		}
		// create new device table
		this->_deviceTable = new char *[numDevice];
		// complete deviceTable
		ptr = this->_deviceList;
		numDevice = 0;
		while( ptr && *ptr )
		{
			this->_deviceTable[numDevice] = ptr;
			while (*ptr != NULL)
				++ptr;
			ptr++;
			numDevice++;
		}
		this->_deviceTable[numDevice] = NULL;
	}
	else
	{
		return NULL;
	}
	return this->_deviceTable;
}

void AudioOut::SelectOutDevice(char *deviceName)
{
	if (this->_currentDevice != NULL)
	{
		alcCloseDevice(this->_currentDevice);
		alcDestroyContext(this->_ctx);
	}
	this->_currentDevice = alcOpenDevice(deviceName);
	this->_ctx = alcCreateContext(this->_currentDevice, NULL);
	alcMakeContextCurrent(this->_ctx);
}

void AudioOut::SelectOutDevice(unsigned int deviceId)
{
	if (this->_currentDevice != NULL)
	{
		alcCloseDevice(this->_currentDevice);
		alcDestroyContext(this->_ctx);
	}
	this->_currentDevice = alcOpenDevice(this->_deviceTable[deviceId]);
	this->_ctx = alcCreateContext(this->_currentDevice, NULL);
	alcMakeContextCurrent(this->_ctx);
}

void AudioOut::AutoSelectOutDevice()
{
	if (this->_currentDevice != NULL)
	{
		alcCloseDevice(this->_currentDevice);
		alcDestroyContext(this->_ctx);
	}
	this->_currentDevice = alcOpenDevice(NULL);
	this->_ctx = alcCreateContext(this->_currentDevice, NULL);
	alcMakeContextCurrent(this->_ctx);
}

void AudioOut::Start()
{
	if (this->_currentDevice == NULL)
		this->AutoSelectOutDevice();
	
	this->checkError("alcMakeContextCurrent");

	std::map<IBuffer*, ISource*>::iterator it = this->_sources.begin();
	std::map<IBuffer*, ISource*>::iterator end = this->_sources.end();

	for(; it != end; ++it)
	{
		(*it).second->Start();
	}
	this->_run = true;
}

void AudioOut::Stop()
{
	std::map<IBuffer*, ISource*>::iterator it = this->_sources.begin();
	std::map<IBuffer*, ISource*>::iterator end = this->_sources.end();

	for(; it != end; ++it)
	{
		(*it).second->Stop();
	}
	this->_run = false;
}


void AudioOut::checkError(char* str)
{
	int error = alGetError();

	if(error != AL_NO_ERROR)
		std::cout << "Error " << error <<  " in : " << str << std::endl;;
}


void AudioOut::treat(IBuffer*buffer, void* data)
{
	int size = buffer->Size();
	ISource *source = reinterpret_cast<ISource*>(data);
	
	if (source->IsRunning())
		source->AddSamples(buffer->Read(size), size);
	else
		buffer->Delete(size);
}

bool			AudioOut::IsRunning()
{
	return this->_run;
}